﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

using SharpDX;

namespace DX11Engine.PlugIns
{
    public class FrecleGiles
    {
        private class GLS_Chunk
        {
            public GilesChunks Type { get; set; }
            public int Size { get; set; }
            public byte[] Data;
            public List<GLS_Chunk> Children { get; set; }
        }

        struct GilesVertex
        {
            public Vector3 Position { get; set; }
            public Vector3 Normals { get; set; }
            public float[] BaseVertexRGBA { get; set; }
            public float[] VertexLightRGB { get; set; }
            public Vector2 LM_UV { get; set; }
            public Vector2 TX_UV { get; set; }
        }

        public static Dictionary<MeshMaterial, PositionNormalTextured[]> LoadMesh(string FileName, float scale = .1f)
        { return (new FrecleGiles()).LoadGilesFile(FileName, scale); }

        public Dictionary<MeshMaterial, PositionNormalTextured[]> LoadGilesFile(string FileName, float scale = .1f)
        {
            MeshMaterial[] Materials = null; TextureLayer[] Textures = null, LightMaps = null;
            Dictionary<MeshMaterial, List<PositionNormalTextured[]>> GilesObject = new Dictionary<MeshMaterial, List<PositionNormalTextured[]>>();

            // Get file data hiearchy:
            GLS_Chunk hieararchy = GetChunks(new GLS_Chunk() { Data = File.ReadAllBytes(FileName) });
            if (hieararchy.Type == GilesChunks.GLS_HEADER)
            {
                foreach (GLS_Chunk crntChnk in hieararchy.Children)
                {
                    switch (crntChnk.Type)
                    {
                        case GilesChunks.GLS_MATERIALS:
                            {
                                if (crntChnk.Children != null)
                                {
                                    Materials = new MeshMaterial[crntChnk.Children.Count];
                                    for (int x = 0; x < crntChnk.Children.Count; x++)
                                    {
                                        Materials[x] = new MeshMaterial() { RGBA = new float[4], TextLayers = new TextureLayer[6] };
                                        foreach (GLS_Chunk matProp in crntChnk.Children[x].Children)
                                        {
                                            switch (matProp.Type)
                                            {
                                                case GilesChunks.GLS_MAT_NAME: { Materials[x].Name = ByteToString(ref matProp.Data, 0); break; }
                                                case GilesChunks.GLS_MAT_RED: { Materials[x].RGBA[0] = BitConverter.ToSingle(matProp.Data, 0); break; }
                                                case GilesChunks.GLS_MAT_GREEN: { Materials[x].RGBA[1] = BitConverter.ToSingle(matProp.Data, 0); break; }
                                                case GilesChunks.GLS_MAT_BLUE: { Materials[x].RGBA[2] = BitConverter.ToSingle(matProp.Data, 0); break; }
                                                case GilesChunks.GLS_MAT_ALPHA: { Materials[x].RGBA[3] = BitConverter.ToSingle(matProp.Data, 0); break; }
                                                case GilesChunks.GLS_MAT_SHININESS: { Materials[x].Specular = BitConverter.ToSingle(matProp.Data, 0); break; }
                                                case GilesChunks.GLS_MAT_LIGHTMAP: { Materials[x].TextLayers[(int)LayerType.LightMap] = LightMaps[BitConverter.ToInt16(matProp.Data, 0) - 1]; break; }
                                                case GilesChunks.GLS_MAT_TEXLAYER:
                                                    {
                                                        // Normally, here we would have stored as a byte indicating the texture layer, and then a short that indicates the texture index-
                                                        // however, I'm just storing the texture index and forcing the texture layer.
                                                        Materials[x].TextLayers[(int)LayerType.Texture] = Textures[BitConverter.ToInt16(matProp.Data, 1)];
                                                        break;
                                                    }
                                            }
                                        }
                                        GilesObject.Add(Materials[x], new List<PositionNormalTextured[]>());
                                    }
                                }
                                break;
                            }

                        case GilesChunks.GLS_TEXTURES:
                            {
                                if (crntChnk.Children != null)
                                {
                                    Textures = new TextureLayer[crntChnk.Children.Count];
                                    for (int x = 0; x < crntChnk.Children.Count; x++)
                                    {
                                        Textures[x] = new TextureLayer();
                                        foreach (GLS_Chunk textProp in crntChnk.Children[x].Children)
                                        {
                                            switch (textProp.Type)
                                            {
                                                case GilesChunks.GLS_TEX_FILE: { Textures[x].FileName = ByteToString(ref textProp.Data, 0); break; }
                                            }
                                        }
                                    }
                                }
                                break;
                            }

                        case GilesChunks.GLS_LIGHTMAPS:
                            {
                                if (crntChnk.Children != null)
                                {
                                    LightMaps = new TextureLayer[crntChnk.Children.Count];
                                    for (int x = 0; x < crntChnk.Children.Count; x++)
                                    {
                                        LightMaps[x] = new TextureLayer() { Type = LayerType.LightMap };
                                        foreach (GLS_Chunk lmProp in crntChnk.Children[x].Children)
                                        {
                                            switch (lmProp.Type)
                                            {
                                                case GilesChunks.GLS_LMAP_FILE: { LightMaps[x].FileName = ByteToString(ref lmProp.Data, 0); break; }
                                            }
                                        }
                                    }
                                }
                                break;
                            }

                        case GilesChunks.GLS_MODELS:
                            {
                                foreach (GLS_Chunk model in crntChnk.Children)
                                {
                                    Vector3 mPos = new Vector3(0, 0, 0);
                                    Vector3 mRot = new Vector3(0, 0, 0);
                                    Vector3 mScale = new Vector3(0, 0, 0);
                                    Vector3 emptyVector = new Vector3(0, 0, 0);
                                    foreach (GLS_Chunk mdlProp in model.Children)
                                    {
                                        switch (mdlProp.Type)
                                        {
                                            case GilesChunks.GLS_MODEL_POSITION: 
                                                {
                                                    mPos = new Vector3(BitConverter.ToSingle(mdlProp.Data, 0),
                                                            BitConverter.ToSingle(mdlProp.Data, 4),
                                                            BitConverter.ToSingle(mdlProp.Data, 8));
                                                    break; 
                                                }
                                            case GilesChunks.GLS_MODEL_ROTATION:
                                                {
                                                    mRot = new Vector3(BitConverter.ToSingle(mdlProp.Data, 0),
                                                            BitConverter.ToSingle(mdlProp.Data, 4),
                                                            BitConverter.ToSingle(mdlProp.Data, 8));
                                                    break;
                                                }
                                            case GilesChunks.GLS_MODEL_SCALE:
                                                {
                                                    mScale = new Vector3(BitConverter.ToSingle(mdlProp.Data, 0),
                                                            BitConverter.ToSingle(mdlProp.Data, 4),
                                                            BitConverter.ToSingle(mdlProp.Data, 8));
                                                    break;
                                                }
                                            case GilesChunks.GLS_MESH:
                                                {
                                                    foreach (GLS_Chunk msh in mdlProp.Children)
                                                    {
                                                        switch (msh.Type)
                                                        {
                                                            case GilesChunks.GLS_MESH_SURFACES:
                                                                {
                                                                    if (msh.Children.Count > 0 && msh.Children[0].Type == GilesChunks.GLS_MESH_SURF)
                                                                    {
                                                                        int Vertices = 0, Polygons = 0, MaterialIndex = 0;
                                                                        GilesVertexData AvailableVertexData = GilesVertexData.TX_UV | GilesVertexData.LM_UV;
                                                                        GilesVertex[] vrtxAry = null; PositionNormalTextured[] plysArray = null;
                                                                        
                                                                        foreach (GLS_Chunk srfProp in msh.Children[0].Children)
                                                                        {
                                                                            switch (srfProp.Type)
                                                                            {
                                                                                case GilesChunks.GLS_MESH_SURFMATERIAL: { MaterialIndex = BitConverter.ToInt16(srfProp.Data, 0); break; }
                                                                                case GilesChunks.GLS_MESH_SURFVERTFORMAT: { AvailableVertexData = (GilesVertexData)BitConverter.ToInt32(srfProp.Data, 0); break; }
                                                                                case GilesChunks.GLS_MESH_SURFPOLYS: 
                                                                                    { 
                                                                                        Polygons = BitConverter.ToInt16(srfProp.Data, 0);
                                                                                        plysArray = new PositionNormalTextured[Polygons * 3];
                                                                                        break; 
                                                                                    }
                                                                                case GilesChunks.GLS_MESH_SURFVERTS:
                                                                                    {
                                                                                        Vertices = BitConverter.ToInt16(srfProp.Data, 0);
                                                                                        vrtxAry = new GilesVertex[Vertices];
                                                                                        break;
                                                                                    }
                                                                                case GilesChunks.GLS_MESH_SURFVERTDATA: 
                                                                                    {
                                                                                        int Offset = 0;
                                                                                        Matrix TransformM = Matrix.Scaling(mScale) * Matrix.RotationYawPitchRoll(mRot.Y, mRot.X, mRot.Z) * Matrix.Translation(mPos);
                                                                                        for (int vrtx = 0; vrtx < Vertices; vrtx++)
                                                                                        {

                                                                                            vrtxAry[vrtx].Position = Vector3.TransformCoordinate(new Vector3(BitConverter.ToSingle(srfProp.Data, Offset),
                                                                                                                                                            BitConverter.ToSingle(srfProp.Data, Offset + 4),
                                                                                                                                                            BitConverter.ToSingle(srfProp.Data, Offset + 8
                                                                                                                                                        )), TransformM) * scale;
                                                                                            Offset += 12;

                                                                                            if ((AvailableVertexData & GilesVertexData.VertexNormals) == GilesVertexData.VertexNormals)
                                                                                            {

                                                                                                vrtxAry[vrtx].Normals = new Vector3(BitConverter.ToSingle(srfProp.Data, Offset),
                                                                                                                    BitConverter.ToSingle(srfProp.Data, Offset + 4),
                                                                                                                    BitConverter.ToSingle(srfProp.Data, Offset + 8));
                                                                                                Offset += 12;
                                                                                            }

                                                                                            if ((AvailableVertexData & GilesVertexData.BaseVertexRGBA) == GilesVertexData.BaseVertexRGBA)
                                                                                            {
                                                                                                float[] BaseVertexRGBA = new float[4];
                                                                                                BaseVertexRGBA[0] = (float)srfProp.Data[Offset] / (float)255; Offset++;
                                                                                                BaseVertexRGBA[1] = (float)srfProp.Data[Offset] / (float)255; Offset++;
                                                                                                BaseVertexRGBA[2] = (float)srfProp.Data[Offset] / (float)255; Offset++;
                                                                                                BaseVertexRGBA[3] = (float)srfProp.Data[Offset] / (float)255; Offset++;
                                                                                                vrtxAry[vrtx].BaseVertexRGBA = BaseVertexRGBA;
                                                                                            }

                                                                                            if ((AvailableVertexData & GilesVertexData.VertexLight) == GilesVertexData.VertexLight)
                                                                                            {
                                                                                                float[] VertexLightRGB = new float[3];
                                                                                                VertexLightRGB[0] = (float)srfProp.Data[Offset] / (float)255; Offset++;
                                                                                                VertexLightRGB[1] = (float)srfProp.Data[Offset] / (float)255; Offset++;
                                                                                                VertexLightRGB[2] = (float)srfProp.Data[Offset] / (float)255; Offset++;
                                                                                                vrtxAry[vrtx].VertexLightRGB = VertexLightRGB;
                                                                                            }

                                                                                            if ((AvailableVertexData & GilesVertexData.LM_UV) == GilesVertexData.LM_UV)
                                                                                            {
                                                                                                vrtxAry[vrtx].LM_UV = new Vector2(BitConverter.ToSingle(srfProp.Data, Offset),
                                                                                                                        BitConverter.ToSingle(srfProp.Data, Offset + 4));
                                                                                                Offset += 8;
                                                                                            }

                                                                                            if ((AvailableVertexData & GilesVertexData.TX_UV) == GilesVertexData.TX_UV)
                                                                                            {
                                                                                                vrtxAry[vrtx].TX_UV = new Vector2(BitConverter.ToSingle(srfProp.Data, Offset),
                                                                                                                        BitConverter.ToSingle(srfProp.Data, Offset + 4));
                                                                                                Offset += 8;
                                                                                            }                                                                                                                
                                                                                        }
                                                                                        break;
                                                                                    }
                                                                                case GilesChunks.GLS_MESH_SURFPOLYDATA:
                                                                                    {
                                                                                        for (int ply = 0; ply < Polygons; ply++)
                                                                                        {
                                                                                            //PositionNormalTextured[] pstnl = new PositionNormalTextured[3];
                                                                                            for(int ti = 0; ti < 3; ti++)
                                                                                            {
                                                                                                GilesVertex gv = vrtxAry[BitConverter.ToInt16(srfProp.Data, (ply * 6) + (ti * 2))];
                                                                                                PositionNormalTextured pnv = new PositionNormalTextured() { 
                                                                                                    Normal = gv.Normals,
                                                                                                    Position = gv.Position,
                                                                                                    Tex = gv.TX_UV,
                                                                                                    LMTex = gv.LM_UV
                                                                                                };
                                                                                                if (Materials[MaterialIndex] != null)
                                                                                                { pnv.Color = new Vector4(Materials[MaterialIndex].RGBA); }
                                                                                                else
                                                                                                {
                                                                                                    if (gv.BaseVertexRGBA != null)
                                                                                                    { pnv.Color = new Vector4(gv.BaseVertexRGBA); }
                                                                                                    else { pnv.Color = new Vector4(1, 1, 1, 1); }
                                                                                                }
                                                                                                plysArray[(ply * 3) + ti] = pnv;
                                                                                                //pstnl[ti] = pnv;
                                                                                            }
                                                                                            //JEntityFactory.GenerateTriangleNormalTangentBinormalData();
                                                                                        }
                                                                                        break;
                                                                                    }
                                                                            }
                                                                        }

                                                                        if (MaterialIndex > -1)
                                                                        {
                                                                            if (GilesObject.ContainsKey(Materials[MaterialIndex]))
                                                                            { GilesObject[Materials[MaterialIndex]].Add(plysArray); }
                                                                        }
                                                                        else { GilesObject.Add(new MeshMaterial(), new List<PositionNormalTextured[]>() { plysArray }); }
                                                                    }
                                                                    break;
                                                                }
                                                        }
                                                    }
                                                    break;
                                                }
                                        }
                                    }
                                }
                                break;
                            } 
                    }
                }
            }
            else { throw new Exception("Invalid Gile[s] header!"); }

            Dictionary<MeshMaterial, PositionNormalTextured[]> exportValue = new Dictionary<MeshMaterial, PositionNormalTextured[]>();
            foreach (KeyValuePair<MeshMaterial, List<PositionNormalTextured[]>> kvp in GilesObject)
            {
                //if (kvp.Value.Count > 0)
                //{
                    List<PositionNormalTextured> tmpList = new List<PositionNormalTextured>();
                    foreach (PositionNormalTextured[] pnt in kvp.Value)
                    {
                        for (int pntIndx = 0; pntIndx < pnt.Length; pntIndx++)
                        { tmpList.Add(pnt[pntIndx]); }
                    }
                    exportValue.Add(kvp.Key, tmpList.ToArray());
                //}
            }
            return exportValue;
        }

        /// <summary>Given an empty chunk with Giles byte data, will iterate and create data hiearchy.</summary>
        /// <param name="ParentChunk">Empty chunk with all Giles format byte data.</param><param name="Offset">Used internally by recursion.</param>
        private GLS_Chunk GetChunks(GLS_Chunk ParentChunk, int Offset = 0)
        {
             // Get chunk type and size:
            ParentChunk.Type = (GilesChunks)BitConverter.ToInt32(ParentChunk.Data, Offset);
            ParentChunk.Size = BitConverter.ToInt32(ParentChunk.Data, Offset + 4); 

            // If header, handle the creation of sub-data differently:
            if (ParentChunk.Type == GilesChunks.GLS_HEADER)
            {
                byte[] filBytAry = ParentChunk.Data; // Get byte data
                int stringLen = ByteToString(ref filBytAry, Offset + 8).Length; // Read application type
                byte[] subData = new byte[ParentChunk.Size - stringLen - 5]; // Declare array the size of the rest of the data
                Array.Copy(ParentChunk.Data, Offset + stringLen + 13, subData, 0, subData.Length); // Copy the rest of the data
                ParentChunk.Data = subData; // Assign as the data for this chunk/node
            }
            else
            {
                byte[] subData = new byte[ParentChunk.Size]; // Get byte data
                Array.Copy(ParentChunk.Data, Offset + 8, subData, 0, subData.Length); // Copy the rest of the data
                ParentChunk.Data = subData; // Assign as the data for this chunk/node
            }

            // Check if we have any data in this node:
            if (ParentChunk.Data != null && ParentChunk.Data.Length > 0)
            {
                // Check if the data is *more* nodes:
                if (HasChildren(ParentChunk.Type))
                {
                    // Create new list of chunks:
                    ParentChunk.Children = new List<GLS_Chunk>();
                    // As long as we are able to, read chunks:
                    int ReadPos = 0;
                    while (ReadPos < ParentChunk.Data.Length)
                    {
                        // Create new child with parent data:
                        GLS_Chunk nwChld = new GLS_Chunk() { Data = ParentChunk.Data };
                        GetChunks(nwChld, ReadPos); // Get child data
                        ParentChunk.Children.Add(nwChld); // Add child to children list
                        ReadPos += nwChld.Size + 8; // Set offset for next child
                    }
                }
            }
            return ParentChunk;
        }

        /// <summary>Returns true is the chunk/node is marked as having children.</summary>
        /// <param name="Type">Chunk Type</param>
        private bool HasChildren(GilesChunks Type)
        {
            return (Type == GilesChunks.GLS_HEADER ||
                    Type == GilesChunks.GLS_LIGHTMAPS ||
                    Type == GilesChunks.GLS_LMAP ||
                    Type == GilesChunks.GLS_MODELS ||
                    Type == GilesChunks.GLS_MODEL ||
                    Type == GilesChunks.GLS_MESH ||
                    Type == GilesChunks.GLS_MESH_SURFACES ||
                    Type == GilesChunks.GLS_MESH_SURF ||
                    Type == GilesChunks.GLS_PIVOT ||
                    Type == GilesChunks.GLS_LIGHT ||
                    Type == GilesChunks.GLS_MATERIALS ||
                    Type == GilesChunks.GLS_MAT ||
                    Type == GilesChunks.GLS_TEXTURES ||
                    Type == GilesChunks.GLS_TEX ||
                    Type == GilesChunks.GLS_RENDER);
        }

        /// <summary>Reads NULL-terminated ASCII string and returns the value.</summary>
        /// <param name="bytArray">Byte array with data</param>
        /// <param name="offset">Offset</param><param name="length">length to enforce if known</param>
        private string ByteToString(ref byte[] bytArray, int offset, int length = 0)
        {
            if (length == 0) {
                for (int i = offset; i < (bytArray.Length - offset); i++)
                { if (bytArray[i] == 0) { return Encoding.ASCII.GetString(bytArray, offset, i - offset); } }
                if (offset < bytArray.Length) { return Encoding.ASCII.GetString(bytArray, offset, bytArray.Length - offset); }
            }
            else { return Encoding.ASCII.GetString(bytArray, offset, length); }
            return null;
        }
    }
}
